/* bolt plugin for gold and/or GNU ld.
   Copyright (C) 2022-2023 Free Software Foundation, Inc.
   Contributed by Majin and Liyancheng.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

/* The plugin has only one external function: onload.  Gold passes it an
   array of function that the plugin uses to communicate back to gold.

   With the functions provided by gold, the plugin can be notified when
   gold first analyzes a file and passes a symbol table back to gold.  The
   plugin is also notified when all symbols have been read and it is time
   to generate machine code for the necessary symbols.

   More information at http://gcc.gnu.org/wiki/whopr/driver.  */

/* Firstly, this plugin read profile info from .text.fdo.func_name section from
   each claim file and parse it into BOLT profile.

   The section read from the claim file will follow the following example.
   .section .text.fdo.sort_array	// Section name
   .string ".fdo.caller sort_array"	// Function name
   .string ".fdo.caller.size 492"	// Function size
   .string ".fdo.caller.bind GLOBAL"	// Bind type
   .string "58"				// branch source address
   .string "0"				// branch destination address
   .string "336"			// count

   The above is the case where the profile data comes from PGO.
   If the data comes from AutoFDO, branch source address will be
   BB address and branch destination address will be disabled. e.g.
   .string "58"				// BB address
   .string "336"			// count

   The BOLT profile file format follows the syntax below which defined in
   llvm-bolt.

   Branch info mode when profile collect from PGO:
   <is symbol?> <closest elf symbol or DSO name> <relative FROM address>
   <is symbol?> <closest elf symbol or DSO name> <relative TO address>
   <number of mispredictions> <number of branches>

   Examples:

   1 main 58 1 main 78 0 100

   BB info mode when profile collect from AutoFDO:
   <is symbol?> <closest elf symbol or DSO name> <relative address> <count>

   Examples:

   1 main 58 100

   Secondly, it also receive BOLT profile generated by perf2bolt.

   Finally, this plugin calls llvm-bolt to do optimizations after linkage.

*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if HAVE_STDINT_H
#include <stdint.h>
#endif
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <filenames.h>
#include <hashtab.h>
#include "simple-object.h"
#include "plugin-api.h"

namespace LIBIBERTY
{
#include <libiberty.h>
}
using LIBIBERTY::xmalloc;
using LIBIBERTY::lbasename;
using LIBIBERTY::xstrdup;
using LIBIBERTY::concat;
using LIBIBERTY::lrealpath;

#include <vector>
#include <string>
#include <map>
#include <set>

using std::vector;
using std::string;
using std::map;
using std::set;

static ld_plugin_register_claim_file register_claim_file = NULL;
static ld_plugin_register_all_symbols_read register_all_symbols_read = NULL;
static ld_plugin_register_cleanup register_cleanup = NULL;
static ld_plugin_message message = NULL;

static enum ld_plugin_output_file_type linker_output;

extern "C"
{
  enum ld_plugin_status onload (struct ld_plugin_tv *tv);
}

/* C99 bool type cannot coerce parm 'gate' range, so use int here.  */

static void
check_gate (int gate, enum ld_plugin_level level, const char *text)
{
  if (gate)
    {
      return;
    }

  if (message)
    {
      message (level, text);
    }
  else
    {
      /* Print msg to stderr if there is no nicer way to inform the user.  */
      fprintf (stderr, "%s\n", text);
      if (level == LDPL_FATAL)
	{
	  abort ();
	}
    }
}

/* This wrapper allows macro CHECK to be called with a non-integer argument
   GATE.  For pointer cases, GATE should be no-Null.  */

#define CHECK(GATE, LEVEL, TEXT) check_gate (((GATE) != 0), (LEVEL), (TEXT))

#define __MSG_INFO__
#define __MSG_WARN__
#define __MSG_ERROR__

#ifdef __MSG_INFO__
#define MSG_INFO(...)						\
  if (message)							\
    {								\
      message (LDPL_INFO, "BOLT-PLUGIN-INFO: " __VA_ARGS__);	\
    }								\
  else								\
    {								\
      fprintf (stderr, "BOLT-PLUGIN-INFO: " __VA_ARGS__);	\
    }
#else
#define MSG_INFO(...)
#endif

#ifdef __MSG_WARN__
#define MSG_WARN(...)							\
  if (message)								\
    {									\
      message (LDPL_WARNING, "BOLT-PLUGIN-WARNING: " __VA_ARGS__);	\
    }									\
  else									\
    {									\
      fprintf (stderr, "BOLT-PLUGIN-WARNING: " __VA_ARGS__);		\
    }
#else
#define MSG_WARN(...)
#endif

#ifdef __MSG_ERROR__
#define MSG_ERROR(...)						\
  if (message)							\
    {								\
      message (LDPL_FATAL, "BOLT-PLUGIN-ERROR: " __VA_ARGS__);	\
    }								\
  else								\
    {								\
      fprintf (stderr, "BOLT-PLUGIN-ERROR: " __VA_ARGS__);	\
      abort ();							\
    }
#else
#define MSG_ERROR(...)
#endif

#if HAVE_DOS_BASED_FILE_SYSTEM
const char *separator = "\\";
#else
const char *separator = "/";
#endif

/* Encapsulates object file data during symbol scan.  */
struct plugin_objfile
{
  simple_object_read *objfile;
  const struct ld_plugin_input_file *file;
};

struct jump_info
{
  string des_func_name;
  string src_addr_offset;
  string dst_addr_offset;
  string count;
};

struct func_info
{
  string function_name;
  string bind_type; /* "GLOBAL","WEAK","LOCAL","UNKNOWN".  */
  string size;
  vector<jump_info> edges;
};

/* Define feedback data type.  */
enum feedback_type
{
  NULL_TYPE,  /* No feedback data.  */
  PGO_TYPE,   /* Feedback data from PGO.  */
  AFDO_TYPE,  /* Feedback data from AutoFDO.  */
  BOLT_TYPE,  /* Feedback data from BOLT.  */
};

#define DEFAULT_BOLT_OUT_DIR (get_current_dir_name ())
#define DEFAULT_BOLT_OUT_NAME "default.fdata"
#define DEFAULT_BOLT_OUT_NAME_SUFFIX ".fdata"

/* The FDO section's special prefix names.  */
#define ASM_FDO_SECTION_PREFIX ".text.fdo."
#define ASM_FDO_CALLER_FLAG ".fdo.caller "
#define ASM_FDO_CALLER_BIND_FLAG ".fdo.caller.bind "
#define ASM_FDO_CALLER_SIZE_FLAG ".fdo.caller.size "
#define ASM_FDO_CALLEE_FLAG ".fdo.callee "

static int linker_output_set;

/* BOLT profile name generated by -fauto-bolt or
   read from -fbolt-use.  */
static string bolt_profile_name;

/* Path to save configuration file generated by -fauto-bolt.  */
static string bolt_dir_path;

/* BOLT profile file FD generated by -fauto-bolt.  */
static FILE *bolt_file_fd = NULL;

/* Temporary binary or dynamic file with reloc info.  */
static string tmp_out_file_name = "a.out";

/* Binary or dynamic file after BOLT.  */
static string bolt_opt_target;

/* Format of bolt_optimize_options should be "reorder-functions=hfsort+ ...",
   command 'llvm-bolt' has been added here.  */
static string bolt_optimize_options ("llvm-bolt ");

static enum feedback_type fdo_type = feedback_type::NULL_TYPE;

static vector<string> gcc_options;

/* Map of <weak_function_name, vector<function_info>>  */
static map<string, vector<struct func_info>> weak_functions;

/* Returns 1 if two strings have the same prefix.  */

inline static int
is_prefix_of (const char *prefix, const char *str)
{
  return strncmp (prefix, str, strlen (prefix)) == 0;
}

static bool
file_exist (const char *file_name)
{
  if (file_name == nullptr)
    {
      MSG_ERROR ("file_exist get empty input file name.");
      return false;
    }
  struct stat buffer;
  if (stat (file_name, &buffer) == 0)
    {
      return true;
    }

  MSG_WARN ("file_exist check failed: %s does not exist!", file_name);
  return false;
}

/* Popen run cmd, use safe character set for whitelist verification.  */

static void
popen_run (const string& cmd)
{
  for (const char &ch : cmd)
    {
      if ((ch >= '0' && ch <= '9')
	  || (ch >= 'A' && ch <= 'Z')
	  || (ch >= 'a' && ch <= 'z')
	  || (ch == ' ' || ch == '_')
	  || (ch == '-' || ch == '/')
	  || (ch == '.' || ch == '+')
	  || (ch == '=' || ch == '#'))
	{
	  continue;
	}
      else
	{
	  MSG_WARN ("Unsafe command: %s", cmd.c_str ());
	  MSG_ERROR ("The command can only contain the following characters "
		     "0-9, A-Z, a-z, '_', '-', '/', ' ', '.', '+', '=', '#' ");
	}
    }
  MSG_INFO ("Execute command: %s", cmd.c_str ());
  FILE *fd = popen (cmd.c_str (), "r");
  if (fd == nullptr)
    {
      MSG_WARN ("Execute command faild!");
    }
  else
    {
      char result_buf[1024];
      while (fgets (result_buf, sizeof (result_buf), fd) != NULL)
	{
	  if (result_buf[strlen (result_buf) - 1] == '\n')
	    {
	      result_buf[strlen (result_buf) - 1] = '\0';
	    }
	  MSG_INFO ("%s", result_buf);
	}
      pclose (fd);
    }
}

/* Generate bolt optimize command.  */

static string
generate_bolt_cmd ()
{
  string new_binary = tmp_out_file_name + ".bolt";
  string cmd;

  /* bolt_optimize_options != "llvm-bolt "
     means that the user uses custom input options.  */
  if (bolt_optimize_options != "llvm-bolt ")
    {
      cmd = bolt_optimize_options + " " + tmp_out_file_name
	+ " -o " + new_binary
	+ " -data=" + bolt_profile_name;
    }
  else
    {
      if (fdo_type == feedback_type::AFDO_TYPE)
	{
	  cmd = string ("llvm-bolt  -reorder-functions=hfsort+ ")
	    + tmp_out_file_name + " -o " + new_binary
	    + " -data=" + bolt_profile_name;
	}
      else if (fdo_type == feedback_type::PGO_TYPE
	  || fdo_type == feedback_type::BOLT_TYPE)
	{
	  cmd = string ("llvm-bolt -reorder-blocks=cache+ ")
	    + string (" -reorder-functions=hfsort+ ")
	    + string (" -split-functions=3 -split-all-cold ")
	    + string (" -dyno-stats -icf=1 -use-gnu-stack ")
	    + tmp_out_file_name + " -o " + new_binary
	    + " -data=" + bolt_profile_name;
	}
      else
	{
	  MSG_ERROR ("Invalid profile type!");
	  return string ();
	}
      MSG_INFO ("Using the default llvm-bolt optimization option,"
		" manually specify this option by -fbolt-option.  ");
    }
  return cmd;
}

/* Execute BOLT optimization, backup original binary with .orig .  */

static void
do_bolt_opt ()
{
  string cmd = generate_bolt_cmd ();
  if (cmd.empty ())
    {
      return;
    }
  popen_run (cmd);
  string new_binary = tmp_out_file_name + ".bolt";
  if (file_exist (new_binary.c_str ()))
    {
      cmd = "mv -f " + tmp_out_file_name + " " + tmp_out_file_name + ".orig";
      popen_run (cmd);

      cmd = "cp -f " + new_binary + " " + tmp_out_file_name;
      popen_run (cmd);
    }
  else
    {
      MSG_ERROR ("BOLT optimization fail!"
		 " Try installing llvm-bolt or"
		 " enabling relocation info with flag -Wl,-q");
    }
}

/* If -fbolt-target is set and this binary is the target, return true.  */

inline static bool
is_bolt_opt_target ()
{
  if (!bolt_opt_target.empty ()
      && strcmp (lbasename (tmp_out_file_name.c_str ()),
      lbasename (bolt_opt_target.c_str ())) != 0)
    {
      MSG_INFO ("BOLT optmization target is %s, processing %s, skip.",
		bolt_opt_target.c_str (), tmp_out_file_name.c_str ());
      return false;
    }
  return true;
}

/* Remove temporary files after linkage, and do BOLT optimization.  */

static enum ld_plugin_status
cleanup_handler ()
{
  if (bolt_file_fd)
    {
      fclose (bolt_file_fd);
    }

  if (file_exist (tmp_out_file_name.c_str ())
      && file_exist (bolt_profile_name.c_str ())
      && is_bolt_opt_target ())
    {
      do_bolt_opt ();
    }

  return LDPS_OK;
}

/* Open BOLT profile file generated by -fauto-bolt.  */

static void
open_bolt_profile_file (const char *file_name)
{
  if (file_name == NULL)
    {
      MSG_ERROR ("Empty BOLT profile name, exit!");
    }

  if (bolt_file_fd == NULL)
    {
      MSG_INFO ("Generate profile file for BOLT: %s", file_name);
      bolt_file_fd = fopen (file_name, "wt");
      if (!bolt_file_fd)
	{
	  MSG_ERROR ("Failed to open the file: %s."
		     " Please check whether the target path exists.",
		     file_name);
	}
      return;
    }
  else
    {
      MSG_WARN ("BOLT profile file: %s is open, skip.", file_name);
    }
}

/* In BOLT profile, function with same name represent as func_name/file_name/1,
   also, `/` has been added in gcc/final.c, so add /1 if this function is same
   name function.  */

static string
add_suffix (string str)
{
  if (str.empty () || (strstr (str.c_str (), "/") == NULL))
    {
      return str;
    }

  return str + "/1";
}

/* Dump function info to BOLT profile, bolt_file_fd does not need
   to be closed here.  */

static void
dump_func_to_bolt_profile_file (const struct func_info &func)
{
  if (func.edges.empty ())
    {
      return;
    }

  if (!bolt_file_fd)
    {
      open_bolt_profile_file (bolt_profile_name.c_str ());

      /* Check whether the feedback data is from AutoFDO.  */
      if (fdo_type == feedback_type::AFDO_TYPE)
       {
	 fprintf (bolt_file_fd, "no_lbr cycles:u:\n");
       }
    }

  for (const auto &edge: func.edges)
    {
      if (fdo_type == feedback_type::PGO_TYPE)
	{
	  fprintf (bolt_file_fd, "1 %s %s 1 %s %s 0 %s\n",
		   add_suffix (func.function_name).c_str (),
		   edge.src_addr_offset.c_str (),
		   add_suffix (edge.des_func_name).c_str (),
		   edge.dst_addr_offset.c_str (), edge.count.c_str ());
	}
      else if (fdo_type == feedback_type::AFDO_TYPE)
	{
	  fprintf (bolt_file_fd, "1 %s %s %s\n",
		   add_suffix (func.function_name).c_str (),
		   edge.src_addr_offset.c_str (),
		   edge.count.c_str ());
	}
    }

  fflush (bolt_file_fd);
}

/* Called by the linker when all symbols have been read.  */

static enum ld_plugin_status
all_symbols_read_handler ()
{
  for (const auto &functions: weak_functions)
    {
      /* More than one weak function.  */
      if (functions.second.size () > 1)
	{
	  MSG_WARN ("The weak function: %s is confusing, take the first one.",
		    functions.first.c_str ());
	}

      dump_func_to_bolt_profile_file (functions.second[0]);
    }
  return LDPS_OK;
}

/* Move pointer p to end and return end.  */

static char *
get_next_content (char *p, char *end)
{
  while (*p && p < end)
    {
      p++;
    }
  p++;

  return p;
}

/* Process function head info.  */

static char *
process_function_head (char *data , char *end, struct func_info *func)
{
  CHECK (is_prefix_of (ASM_FDO_CALLER_FLAG, data), LDPL_FATAL,
	 "The function name is missing.");
  func->function_name = xstrdup (data + strlen (ASM_FDO_CALLER_FLAG));
  data = get_next_content (data, end);

  CHECK (is_prefix_of (ASM_FDO_CALLER_SIZE_FLAG, data), LDPL_FATAL,
	 "The function size is missing.");
  func->size = xstrdup (data + strlen (ASM_FDO_CALLER_SIZE_FLAG));
  data = get_next_content (data, end);

  CHECK (is_prefix_of (ASM_FDO_CALLER_BIND_FLAG, data), LDPL_FATAL,
	 "The function bind type is missing.");
  func->bind_type = xstrdup (data + strlen (ASM_FDO_CALLER_BIND_FLAG));
  data = get_next_content (data, end);
  return data;
}

/* Read profile info from the symbol table located between data and end.  */

static void
process_section (char *data, char *end)
{
  struct func_info func;

  data = process_function_head (data, end, &func);

  while (*data && data < end)
    {
      struct jump_info jump;

      CHECK (data, LDPL_FATAL, "data is NULL");
      jump.src_addr_offset = xstrdup (data);

      data = get_next_content (data, end);
      CHECK (data, LDPL_FATAL, "data is NULL");
      if (is_prefix_of (ASM_FDO_CALLEE_FLAG, data))
	{
	  jump.des_func_name = xstrdup (data + strlen (ASM_FDO_CALLEE_FLAG));
	  jump.dst_addr_offset = "0";
	  data = get_next_content (data, end);
	  CHECK (data, LDPL_FATAL, "data is NULL");
	}
      else if (fdo_type == feedback_type::PGO_TYPE)
	{
	  jump.des_func_name = func.function_name;
	  jump.dst_addr_offset = xstrdup (data);
	  data = get_next_content (data, end);
	  CHECK (data, LDPL_FATAL, "data is NULL");
	}
      else
	{
	  jump.des_func_name = func.function_name;
	}

      jump.count = xstrdup (data);
      data = get_next_content (data, end);

      func.edges.push_back (jump);
    }

  if (is_prefix_of ("WEAK", func.bind_type.c_str ()))
    {
      weak_functions[func.function_name].push_back (func);
    }
  else
    {
      dump_func_to_bolt_profile_file (func);
    }
}

/* Process error when calling function process_symtab.  */

static int
process_symtab_error (struct plugin_objfile *obj, char *secdatastart)
{
  MSG_ERROR ("%s: corrupt object file.", obj->file->name);

  /* Force claim_file_handler to abandon this file.  */
  if (secdatastart != NULL)
    {
      free (secdatastart);
    }
  return 0;
}

/* Process one section of an object file.  Return to 1 to continue processing
   other sections which define in simple_object_find_sections.  */

static int
process_symtab (void *data, const char *name, off_t offset, off_t length)
{
  if (data == NULL)
    {
      MSG_WARN ("Empty symtab! skip it.");
      return 0;
    }
  if (name == NULL)
    {
      MSG_WARN ("Empty symtab name! skip it.");
      return 0;
    }
  struct plugin_objfile *obj = (struct plugin_objfile *)data;
  char *secdatastart;
  char *secdata;

  if (!is_prefix_of (ASM_FDO_SECTION_PREFIX, name))
    {
      return 1;
    }

  secdata = secdatastart = (char *)xmalloc (length * sizeof (char));
  offset += obj->file->offset;
  if (offset != lseek (obj->file->fd, offset, SEEK_SET))
    {
      return process_symtab_error (obj, secdatastart);
    }

  do
    {
      ssize_t got = read (obj->file->fd, secdata, length);

      if (got == 0)
	{
	  break;
	}
      else if (got > 0)
	{
	  secdata += got;
	  length -= got;
	}
      else if (errno != EINTR)
	{
	  return process_symtab_error (obj, secdatastart);
	}
     }
  while (length > 0);

  if (length > 0)
    {
      return process_symtab_error (obj, secdatastart);
    }

  process_section (secdatastart, secdata);
  free (secdatastart);
  return 1;
}

/* Callback used by gold to check if the plugin will claim FILE.  Writes
   the result in CLAIMED.  */

static enum ld_plugin_status
claim_file_handler (const struct ld_plugin_input_file *file, int *claimed)
{
  struct plugin_objfile obj;
  int err;
  const char *errmsg = NULL;
  /* If file is empty, bolt plugin do nothing and return ok.  */
  if (file == NULL)
    {
      return LDPS_OK;
    }
  /* BOLT plugin does not need claimd number, so set *claimed to 0.  */
  *claimed = 0;

  obj.file = file;
  obj.objfile = simple_object_start_read (file->fd, file->offset, NULL,
					  &errmsg, &err);

  /* No file, but also no error code means unrecognized format,
     skip it.  */
  if (!obj.objfile && !err)
    {
      return LDPS_OK;
    }

  if (obj.objfile)
    {
      simple_object_find_sections (obj.objfile, process_symtab, &obj, &err);
      simple_object_release_read (obj.objfile);
    }

  return LDPS_OK;
}

/* Mangle filename path of BASE and output new allocated pointer with
   mangled path.  */

static string
mangle_path (const string &base)
{
  if (base.empty ())
    {
      return base;
    }

  /* Convert '/' to '#', convert '..' to '^',
     convert ':' to '~' on DOS based file system.  */

  string new_path;
  int base_len = base.size ();
  int l = 0;
  int r = 0;
  while (l < base_len)
    {
      while (r < base_len && base[r] != '/')
	{
	  r++;
	}

      int len = r - l;
      if (len == 2 && base[r - 2] == '.' && base[r - 1] == '.')
	{
	  new_path += '^';
	}
      else
	{
	  new_path += base.substr (l, r - l);
	}
      if (r < base_len)
	{
	  new_path += '#';
	}

      r++;
      l = r;
    }
  return new_path;
}

/* Generate BOLT profile name from file_name.  */

static string
generate_bolt_profile_name (string file_name)
{
  if (!IS_ABSOLUTE_PATH (file_name.c_str ()))
    {
      if (!bolt_dir_path.empty ())
	{
	  file_name = concat (get_current_dir_name (),
			      separator, file_name.c_str (), NULL);
	  file_name = mangle_path (file_name);
	}
      else
	{
	  bolt_dir_path = DEFAULT_BOLT_OUT_DIR;
	}
    }
  file_name = concat (bolt_dir_path.c_str (), separator, file_name.c_str (),
		      NULL);
  return file_name;
}

/* Match option_prefix from gcc_options, return the index of gcc_options.  */

static int
match_gcc_option (const char *option_prefix)
{
  if (option_prefix == NULL)
    {
      return -1;
    }

  for (size_t i = 0; i < gcc_options.size (); i++)
    {
      if (is_prefix_of (option_prefix, gcc_options[i].c_str ()))
	{
	  return i;
	}
    }

  return -1;
}

/* Get options form environment COLLECT_GCC_OPTIONS.  */

static void
get_options_from_collect_gcc_options (const char *collect_gcc,
				      const char *collect_gcc_options)
{
  /* When using GCC, collect_gcc will not be empty.  */
  if (collect_gcc == NULL || collect_gcc_options == NULL)
    {
      return;
    }

  size_t len = strlen (collect_gcc_options);
  size_t r = 0;
  while (r < len && collect_gcc_options[r] != '\0')
    {
      if (collect_gcc_options[r] == '\'')
	{
	  string option;
	  ++r;
	  do
	    {
	      if (collect_gcc_options[r] == '\0')
		{
		  MSG_ERROR ("Malformed COLLECT_GCC_OPTIONS");
		}
	      else if (is_prefix_of ("'\\''", &collect_gcc_options[r]))
		{
		  option.push_back ('\'');
		  r += 4;
		}
	      else if (collect_gcc_options[r] == '\'')
		{
		  break;
		}
	      else
		{
		  option.push_back (collect_gcc_options[r]);
		  ++r;
		}
	    }
	  while (1);

	  if (!option.empty ())
	    {
	      gcc_options.push_back (option);
	    }
	}
      ++r;
    }
}

/* Substitute comma with space in RAW_STRING, used for parser
  -fbolt-option.  */

static string
parser_bolt_optimize_option (string raw_string)
{
  for (auto &ch : raw_string)
    {
      if (ch == ',')
	{
	  ch = ' ';
	}
    }

  return raw_string;
}

/* Process option -fauto-bolt.  */

static void
process_auto_bolt_option (const string &flag_auto_bolt)
{
  const int auto_bolt_index = match_gcc_option (flag_auto_bolt.c_str ());

  if (auto_bolt_index != -1)
    {
      if (gcc_options[auto_bolt_index] == "-fauto-bolt")
	{
	  MSG_INFO ("Use default output directory %s, ", DEFAULT_BOLT_OUT_DIR);
	  MSG_INFO ("Specify it using -fauto-bolt= if needed.");
	}
      else
	{
	  string flag_auto_bolt_equal = "-fauto-bolt=";
	  bolt_dir_path = lrealpath (gcc_options[auto_bolt_index].substr (
			  flag_auto_bolt_equal.size ()).c_str ());
	  MSG_INFO ("Get bolt profile path: %s", bolt_dir_path.c_str ());
	}
      bolt_profile_name = generate_bolt_profile_name(bolt_profile_name);
    }
}

/* Process option -fbolt-use=.  */

static void
process_bolt_use_option (const string &flag_bolt_use)
{
  const int bolt_use_index = match_gcc_option (flag_bolt_use.c_str ());

  if (bolt_use_index != -1)
    {
      /* bolt_profile_name may be initialized in
	 function process_output_option.  */
      bolt_profile_name = gcc_options[bolt_use_index].substr (
			  flag_bolt_use.size ()).c_str ();
      if (bolt_profile_name.empty ())
	{
	  bolt_profile_name = DEFAULT_BOLT_OUT_NAME;
	}
      MSG_INFO ("Get bolt profile: %s", bolt_profile_name.c_str ());
    }
}

/* Process option -fbolt-target=.  */

static void
process_bolt_target_option (const string &flag_bolt_target)
{
  const int bolt_target_index = match_gcc_option (flag_bolt_target.c_str ());
  if (bolt_target_index != -1)
    {
      bolt_opt_target = gcc_options[bolt_target_index].substr (
	flag_bolt_target.size ()).c_str ();
      MSG_INFO ("Get bolt target: %s", bolt_opt_target.c_str ());
    }
}

/* Process option -fbolt-option=.  */

static void
process_bolt_option (const string &flag_bolt_optimize_options)
{
  const int bolt_optimize_options_index
    = match_gcc_option (flag_bolt_optimize_options.c_str ());

  if (bolt_optimize_options_index != -1)
    {
      bolt_optimize_options.append (parser_bolt_optimize_option (
	gcc_options[bolt_optimize_options_index].substr (
	flag_bolt_optimize_options.size ()).c_str ()));

      MSG_INFO ("Get bolt optimize options is %s",
	bolt_optimize_options.c_str ());
    }
}

/* If -o is specified, set binary name and bolt profile name.  This
   function must be called before the process_bolt_use_option function.  */

static void
process_output_option (const string &flag_o)
{
  const int o_index = match_gcc_option (flag_o.c_str ());
  if (o_index != -1)
    {
      tmp_out_file_name = gcc_options[o_index + 1];
      /* bolt_profile_name may be overridden in
	 function process_auto_bolt_option and
	 process_bolt_use_option.  */
      bolt_profile_name = gcc_options[o_index + 1];
      bolt_profile_name.append (DEFAULT_BOLT_OUT_NAME_SUFFIX);
    }
  else
    {
      bolt_profile_name = DEFAULT_BOLT_OUT_NAME;
      MSG_INFO ("Use default file name %s, specify it using -o if needed.",
		DEFAULT_BOLT_OUT_NAME);
    }
}

/* Parse the plugin options.  */

static void
process_gcc_option ()
{
  string flag_profile_use = "-fprofile-use";
  string flag_auto_profile = "-fauto-profile";
  string flag_auto_bolt = "-fauto-bolt";
  string flag_bolt_use = "-fbolt-use=";
  string flag_bolt_target = "-fbolt-target=";
  string flag_bolt_optimize_options = "-fbolt-option=";
  string flag_o = "-o";

  char *collect_gcc = getenv ("COLLECT_GCC");
  char *collect_gcc_option = getenv ("COLLECT_GCC_OPTIONS");

  get_options_from_collect_gcc_options (collect_gcc, collect_gcc_option);

  /* Function process_output_option should be processed before
     process_auto_bolt_option to obtain correct bolt_profile_name.  */
  process_output_option (flag_o);
  process_auto_bolt_option (flag_auto_bolt);
  process_bolt_use_option (flag_bolt_use);
  process_bolt_target_option (flag_bolt_target);
  process_bolt_option (flag_bolt_optimize_options);
  
  if (match_gcc_option (flag_profile_use.c_str ()) != -1)
    {
      fdo_type = feedback_type::PGO_TYPE;
    }
  else if (match_gcc_option (flag_auto_profile.c_str ()) != -1)
    {
      fdo_type = feedback_type::AFDO_TYPE;
    }

  if (match_gcc_option (flag_bolt_use.c_str ()) != -1)
    {
      fdo_type = feedback_type::BOLT_TYPE;
    }

  if (fdo_type == feedback_type::NULL_TYPE)
    {
      MSG_ERROR ("No feedback data, maybe use -fprofile-use "
		 "-fbolt-use or -fauto-profile.");
    }
}

/* Register callback function including all_symbols_read_handler,
   cleanup_handler and claim_file_handler.  */

static void
register_callback_function ()
{
  enum ld_plugin_status status;

  if (linker_output_set && linker_output != LDPO_EXEC)
    {
      MSG_INFO ("This linker[%d] is not for exec, just skip.", linker_output);
      return;
    }

  CHECK (register_claim_file, LDPL_FATAL, "register_claim_file not found");
  status = register_claim_file (claim_file_handler);
  CHECK (status == LDPS_OK, LDPL_FATAL,
	 "could not register the claim_file callback");

  if (register_cleanup)
    {
      status = register_cleanup (cleanup_handler);
      CHECK (status == LDPS_OK, LDPL_FATAL,
	     "could not register the cleanup callback");
    }

  if (register_all_symbols_read)
    {
      status = register_all_symbols_read (all_symbols_read_handler);
      CHECK (status == LDPS_OK, LDPL_FATAL,
	     "could not register the all_symbols_read callback");
    }
}

/* Called by gold after loading the plugin.  TV is the transfer vector.  */

enum ld_plugin_status
onload (struct ld_plugin_tv *tv)
{
  struct ld_plugin_tv *p;

  p = tv;
  while (p->tv_tag)
    {
      switch (p->tv_tag)
	{
	  case LDPT_MESSAGE:
	    message = p->tv_u.tv_message;
	    break;
	  case LDPT_REGISTER_CLAIM_FILE_HOOK:
	    register_claim_file = p->tv_u.tv_register_claim_file;
	    break;
	  case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK:
	    register_all_symbols_read = p->tv_u.tv_register_all_symbols_read;
	    break;
	  case LDPT_REGISTER_CLEANUP_HOOK:
	    register_cleanup = p->tv_u.tv_register_cleanup;
	    break;
	  case LDPT_LINKER_OUTPUT:
	    linker_output = (enum ld_plugin_output_file_type)p->tv_u.tv_val;
	    linker_output_set = 1;
	    break;
	  default:
	    break;
	}
      p++;
    }

  register_callback_function ();
  process_gcc_option ();

  return LDPS_OK;
}

